/*
 * Copyright 2010 WANdisco, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_fs.h"
#include "svn_props.h"
#include "svn_repos.h"
#include "svn_base64.h"
#include "svn_dirent_uri.h"

#include "com_googlecode_svnj_RepoContext.h"

#include "jniutil.h"

/* RepoContext fields */
static jfieldID fid_uuid, fid_youngestRev, fid_ptr;
/* OutputStream.write(byte[]) */
static jmethodID mid_ostream_write;
/* RepoContext.createEntry */
static jmethodID mid_createEntry;
/* HashMap.<init> */
static jmethodID mid_map_ctor;
/* Map.put(String,String) */
static jmethodID mid_map_put;
/* callbacks.ChangePath.<init>(String,long,String,char,int) */
static jmethodID mid_changepath_ctor;
/* callbacks.LogMessageCallback.singleMessage(ChangePath[],long,Map,boolean) */
static jmethodID mid_log_callback;
/* callbacks.FileRevCallback.singleRevision(String,long,Map,boolean,Map,boolean) */
static jmethodID mid_filerev_callback;
/* callbacks.FileRevCallback.appendTextDelta(String) */
static jmethodID mid_filerev_txdelta;
/* callbacks.FileRevCallback.endTextDelta() */
static jmethodID mid_filerev_end_delta;
/* callbacks.LocationSegment.<init>(String,long,long) */
static jmethodID mid_segment_ctor;
/* callbacks.LocationSegmentCallback.singleSegment(LocationSegment) */
static jmethodID mid_segment_callback;

/* create all per-request sub pools out of here */
static apr_pool_t *master_pool;

typedef struct repo_ctx {
  svn_repos_t *repos;
  svn_fs_t *fs;
  svn_fs_root_t *rev_root;
  apr_pool_t *pool;
} repo_ctx;

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_initFields(JNIEnv *env, jclass repo_clazz) {
  jclass clazz_ostream, clazz_map, clazz;
  /* RepoContext fields and methods */
  fid_uuid = (*env)->GetFieldID(env, repo_clazz, "uuid", "Ljava/lang/String;");
  if (!fid_uuid) return;
  fid_youngestRev = (*env)->GetFieldID(env, repo_clazz, "youngestRev", "J");
  if (!fid_youngestRev) return;
  fid_ptr = (*env)->GetFieldID(env, repo_clazz, "ptr", "J");
  if (!fid_ptr) return;
  mid_createEntry = (*env)->GetStaticMethodID(env, repo_clazz, "createEntry",
                                              "(Ljava/lang/String;IJ)"
                                              "L" SVNJPKG "/RepoContext$DirEntry;");
  if (!mid_createEntry) return;

  /* OutputStream.write() */
  clazz_ostream = (*env)->FindClass(env, "java/io/OutputStream");
  if (!clazz_ostream) return;
  mid_ostream_write = (*env)->GetMethodID(env, clazz_ostream, "write", "([BII)V");
  if (!mid_ostream_write) return;

  /* Map and HashMap */
  clazz_map = (*env)->FindClass(env, "java/util/Map");
  if (!clazz_map) return;
  mid_map_put = (*env)->GetMethodID(env, clazz_map, "put",
                                    "(Ljava/lang/Object;Ljava/lang/Object;)"
                                    "Ljava/lang/Object;");
  if (!mid_map_put) return;
  clazz_map = (*env)->FindClass(env, "java/util/HashMap");
  if (!clazz_map) return;
  mid_map_ctor = (*env)->GetMethodID(env, clazz_map, "<init>",
                                     "()V");
  if (!mid_map_ctor) return;

  /* callbacks.ChangePath */
  clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/ChangePath");
  if (!clazz) return;
  mid_changepath_ctor = (*env)->GetMethodID(env, clazz, "<init>",
                           "(Ljava/lang/String;JLjava/lang/String;CIZZ)V");
  if (!mid_changepath_ctor) return;

  /* callbacks.LogMessageCallback */
  clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/LogMessageCallback");
  if (!clazz) return;
  mid_log_callback = (*env)->GetMethodID(env, clazz, "singleMessage",
                           "([L" SVNJPKG "/callbacks/ChangePath;JLjava/util/Map;Z)V");
  if (!mid_log_callback) return;

  /* callbacks.FileRevCallback */
  clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/FileRevCallback");
  if (!clazz) return;
  mid_filerev_callback = (*env)->GetMethodID(env, clazz, "singleRevision",
                                         "(Ljava/lang/String;JLjava/util/Map;ZLjava/util/Map;Z)V");
  if (!mid_filerev_callback) return;
  mid_filerev_txdelta =  (*env)->GetMethodID(env, clazz, "appendTextDelta",
                                         "(Ljava/lang/String;)V");
  if (!mid_filerev_txdelta) return;
  mid_filerev_end_delta =  (*env)->GetMethodID(env, clazz, "endTextDelta", "()V");
  if (!mid_filerev_end_delta) return;

  /* callbacks.LocationSegment[Callback] */
  clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/LocationSegment");
  if (!clazz) return;
  mid_segment_ctor = (*env)->GetMethodID(env, clazz, "<init>",
                                         "(Ljava/lang/String;JJ)V");
  if (!mid_segment_ctor) return;
  clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/LocationSegmentCallback");
  if (!clazz) return;
  mid_segment_callback = (*env)->GetMethodID(env, clazz, "singleSegment",
                                         "(L" SVNJPKG "/callbacks/LocationSegment;)V");
  if (!mid_segment_callback) return;

  apr_initialize();
  
  master_pool = svn_pool_create(NULL);
  
}

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_initSelf(JNIEnv *env, jobject self,
                                            jstring jrepo,
                                            jlong rootRev) {

  /* init object and pool */
  apr_pool_t *pool = svn_pool_create(master_pool);
  repo_ctx *ctx = apr_pcalloc(pool, sizeof(struct repo_ctx));
  svn_error_t *err;
  const char *path;
  const char *uuid;
  svn_revnum_t rev;
  
  ctx->pool = pool;
  (*env)->SetLongField(env, self, fid_ptr, (jlong)ctx);
  /* open repos */
  path = make_cstring(env, jrepo, ctx->pool);
  err = svn_repos_open(&(ctx->repos), path, ctx->pool);
  if (err != SVN_NO_ERROR) {
    throw_svn_exception(env, err, FALSE);
    return;
  }
  ctx->fs = svn_repos_fs(ctx->repos);
  /* get uuid */
  err = svn_fs_get_uuid(ctx->fs, &uuid, ctx->pool);
  if (err != SVN_NO_ERROR) {
    throw_svn_exception(env, err, FALSE);
    return;
  }

  /* open root */

  (*env)->SetObjectField(env, self, fid_uuid, make_jstring(env, uuid));
  err = svn_fs_youngest_rev(&rev, ctx->fs, ctx->pool);
  if (err != SVN_NO_ERROR) {
    throw_svn_exception(env, err, FALSE);
    return;
  }
  (*env)->SetLongField(env, self, fid_youngestRev, (jlong)rev);
  if (rootRev < 0) {
    rootRev = (jlong)rev;
  }
  err = svn_fs_revision_root(&(ctx->rev_root), ctx->fs, rootRev, ctx->pool);
  if (err != SVN_NO_ERROR) {
    throw_svn_exception(env, err, FALSE);
    return;
  }
}

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_cleanupSelf(JNIEnv *env, jobject self) {
  repo_ctx *ctx;
  jlong ptr = (*env)->GetLongField(env, self, fid_ptr);
  if (ptr) {
    ctx = (repo_ctx*)ptr;
    svn_fs_close_root(ctx->rev_root);
    svn_pool_destroy(ctx->pool);
    (*env)->SetLongField(env,self,fid_ptr,(jlong)0);
  }
}

JNIEXPORT jstring JNICALL
Java_com_googlecode_svnj_RepoContext_md5(JNIEnv *env, jobject self, jstring jpath) {
  repo_ctx *ctx;
  svn_checksum_t *checksum;
  const char *value, *path;
  jlong ptr = (*env)->GetLongField(env, self, fid_ptr);
  if (!jpath) {
    throw_exception(env, "java/lang/NullPointerException", "NULL path");
    return NULL;
  }
  
  ctx = (repo_ctx*)ptr;
  path = make_cstring(env, jpath, ctx->pool);

  SVN_JNI_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
                                   ctx->rev_root, path, TRUE, ctx->pool), NULL);
  value = svn_checksum_to_cstring(checksum, ctx->pool);
  return make_jstring(env, value);
}

static apr_array_header_t*
apr_array(JNIEnv *env, jobject string_array, apr_pool_t *pool) {
  jint len, i;
  jstring str;
  apr_array_header_t *ret;

  if (string_array == NULL) return NULL;
  len = (*env)->GetArrayLength(env, string_array);
  ret = apr_array_make(pool, len, sizeof(char*));
  for (i = 0; i < len; i++) {
    str = (jstring)(*env)->GetObjectArrayElement(env, string_array, i);
    *(const char**)apr_array_push(ret) = make_cstring(env, str, pool);
  }
  return ret;
  
}
/* array of svn_prop_t: char *name, svn_string_t *value */
static jobject
prop_array_to_map(JNIEnv *env, apr_array_header_t *array) {

  jobject ret;
  jclass map_clazz;
  int i;

  map_clazz = (*env)->FindClass(env, "java/util/HashMap");
  ret = (*env)->NewObject(env, map_clazz, mid_map_ctor);
  for (i = 0; i < array->nelts; i++) {
    jstring jname, jvalue;
    const svn_prop_t *prop = &APR_ARRAY_IDX(array, i, svn_prop_t);
    jname = make_jstring(env, prop->name);
    jvalue = make_jstring2(env, prop->value);
    (*env)->CallObjectMethod(env, ret, mid_map_put, jname, jvalue);
  }
  return ret;
}

static jobject
hash2map(JNIEnv *env, apr_hash_t *hash, apr_pool_t *pool, jobject jmap) {
  apr_hash_index_t *hi;

  if (jmap == NULL) {
    /* create a hashmap */
    jclass map_clazz = (*env)->FindClass(env, "java/util/HashMap");
    jmap = (*env)->NewObject(env, map_clazz, mid_map_ctor);
  }
  for (hi = apr_hash_first(pool, hash); hi; hi = apr_hash_next(hi)) {
    const char *key;
    svn_string_t *val;
    jstring jname, jval;

    apr_hash_this(hi, (const void **)&key, NULL, (void **)&val);

    jname = make_jstring(env, key);
    jval = make_jstring2(env, val);
    (*env)->CallObjectMethod(env, jmap, mid_map_put, jname, jval);
  }
  return jmap;
}

static jboolean
tristate_to_bool(svn_tristate_t ts) {
  return ts == svn_tristate_true;
}
/* create ChangePath java object given svn_log_changed_path2_t */
static jobject
create_change_path(JNIEnv *env, const char* path, svn_log_changed_path2_t *cp) {
  jstring jpath, jcopy_path;
  jobject ret;
  jclass cp_clazz;
  
  cp_clazz = NULL;
  cp_clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/ChangePath");
  jpath = make_jstring(env, path);
  jcopy_path = make_jstring(env, cp->copyfrom_path);
  ret = (*env)->NewObject(env, cp_clazz, mid_changepath_ctor, jpath,
                          (jlong)cp->copyfrom_rev, jcopy_path,
                          (jchar)cp->action, cp->node_kind,
                          tristate_to_bool(cp->text_modified),
                          tristate_to_bool(cp->props_modified)
    );
  return ret;
}

typedef struct {
  JNIEnv *env;
  jobject jcallback;
  //repo_ctx *ctx;
} log_baton;

static svn_error_t *
svnj_log_receiver(void *baton, svn_log_entry_t *log_entry, apr_pool_t *pool) {
  log_baton *log = (log_baton*)baton;
  jobjectArray jpaths = NULL;
  jobject jpropMap = NULL;
  int size;
  jclass cp_clazz;
  JNIEnv *env;

  /*fprintf(stderr, "[repo-ctx.c] log callback on %ld\n", log_entry->revision);*/
  env = log->env;

  if (log_entry->changed_paths) {
    int i = 0;
    apr_hash_index_t *hi;

    cp_clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/ChangePath");
    size = apr_hash_count(log_entry->changed_paths);
    jpaths = (*env)->NewObjectArray(env, size, cp_clazz, NULL);
    for (hi = apr_hash_first(pool, log_entry->changed_paths2);
         hi != NULL; hi = apr_hash_next(hi), i++) {
      const char *path;
      svn_log_changed_path2_t *cp;
      jobject jcp;

      apr_hash_this(hi, (const void**)&path, NULL, (void**)&cp);
      jcp = create_change_path(env, path, cp);
      (*env)->SetObjectArrayElement(env, jpaths, i, jcp);
        
    }
  }
  if (log_entry->revprops) {
    jpropMap = hash2map(env, log_entry->revprops, pool, NULL);
  }
  /* make the callback */
  (*env)->CallVoidMethod(env, log->jcallback, mid_log_callback, jpaths,
                         (jlong)log_entry->revision, jpropMap, log_entry->has_children);

  /* blow out of the SVN C stack if java exception pending */
  if (IS_JAVA_EXCEPTION()) {
    return exception_to_svn_err(env);
  }
  return SVN_NO_ERROR;
}

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_getLogs
  (JNIEnv *env, jobject self, jobject jcallback, jobjectArray jpaths, jlong jstartRev,
   jlong jendRev, jint limit, jboolean discover_changed_paths, jboolean strict_node_history,
   jboolean include_merged_revisions, jobjectArray jrevProps) {
  repo_ctx *ctx;
  jlong ptr;
  svn_revnum_t start, end;
  log_baton baton;
  apr_array_header_t *paths, *revprops;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, );
  ctx = (repo_ctx*)ptr;
  if (jcallback == NULL) {
    throw_exception(env, "java/lang/NullPointerException", "null log callback");
    return;
  }
  baton.env = env;
  baton.jcallback = jcallback;
  start = jstartRev;
  end = jendRev;
  paths = apr_array(env, jpaths, ctx->pool);
  revprops = apr_array(env, jrevProps, ctx->pool);
  SVN_JNI_ERR(
  svn_repos_get_logs4(
    ctx->repos, paths, start, end, limit, 
    discover_changed_paths, strict_node_history,
    include_merged_revisions,
    revprops, NULL/*authz_func*/, NULL/*authz_baton*/,
    svnj_log_receiver, &baton, ctx->pool
    ), );
}

typedef struct {
  JNIEnv *env;
  jobject jcallback;
} segment_baton;

/* implements svn_location_segment_receiver_t */
static svn_error_t*
svnj_location_receiver(svn_location_segment_t *seg,
                       void *baton, apr_pool_t *pool) {
  JNIEnv *env;
  jstring jpath;
  jobject jsegment;
  jclass segment_clazz;
  segment_baton *sb = baton;
  env = sb->env;

  jpath = make_jstring(env, seg->path);
  segment_clazz = (*env)->FindClass(env, SVNJPKG "/callbacks/LocationSegment");
  jsegment = (*env)->NewObject(env, segment_clazz, mid_segment_ctor, jpath,
                               (jlong)seg->range_start, (jlong)seg->range_end);
  (*env)->CallVoidMethod(env, sb->jcallback, mid_segment_callback, jsegment);
  SVNJ_JAVA_CHECK();
  
}

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_getLocationSegments
  (JNIEnv *env, jobject self, jstring jpath, jlong jpegRev, jlong jstartRev,
   jlong jendRev, jobject jcallback) {
  repo_ctx *ctx;
  jlong ptr;
  svn_revnum_t start, end;
  const char *path;
  segment_baton baton;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, );
  ctx = (repo_ctx*)ptr;
  if (jcallback == NULL) {
    throw_exception(env, "java/lang/NullPointerException", "null segment callback");
    return;
  }
  if (jpath == NULL) {
    throw_exception(env, "java/lang/NullPointerException", "null path");
    return;
  }
  baton.env = env;
  baton.jcallback = jcallback;
  start = jstartRev;
  end = jendRev;
  path = make_cstring(env, jpath, ctx->pool);
  SVN_JNI_ERR(
    svn_repos_node_location_segments(ctx->repos,
                                     path,
                                     (svn_revnum_t)jpegRev,
                                     start,
                                     end,
                                     svnj_location_receiver,
                                     &baton,
                                     NULL, /*authz_read_func */
                                     NULL, /*authz_read_baton */
                                     ctx->pool),
    );
      
}

struct window_handler_baton
{
  JNIEnv *env;
  jobject jcallback;
  svn_boolean_t seen_first_window;  /* False until first window seen. */
  /* The _real_ window handler and baton. */
  svn_txdelta_window_handler_t handler;
  void *handler_baton;
};

/* implements svn_txdelta_window_handler_t */
static svn_error_t *
svnj_window_handler(svn_txdelta_window_t *window,
                    void *baton)
{
  struct window_handler_baton *wb = baton;
  SVN_ERR(wb->handler(window, wb->handler_baton));
  if (!window) {
    /*printf("[handler2] end window\n");*/
  }
  return SVN_NO_ERROR;
}

/* stream handling */
static svn_error_t *
svnj_write_stream(void *baton, const char *buffer, apr_size_t *len) {
  svn_string_t str;
  jstring jstr;
  struct window_handler_baton *wb = baton;
  JNIEnv *env = wb->env;

  str.data = buffer;
  str.len = *len;
  jstr = make_jstring2(env, &str);

  (*env)->CallVoidMethod(env, wb->jcallback, mid_filerev_txdelta, jstr);

  SVNJ_JAVA_CHECK();
}

static svn_error_t *
svnj_close_stream(void *baton) {
  struct window_handler_baton *wb = baton;
  JNIEnv *env = wb->env;
  (*env)->CallVoidMethod(env, wb->jcallback, mid_filerev_end_delta);

  SVNJ_JAVA_CHECK();
}

typedef struct {
  JNIEnv *env;
  jobject jcallback;
  //repo_ctx *ctx;
} file_rev_baton;

/* This implements the svn_repos_file_rev_handler2_t interface. */
static svn_error_t *
file_rev_handler(void *vbaton,
                 const char *path,
                 svn_revnum_t revnum,
                 apr_hash_t *rev_props,
                 svn_boolean_t merged_revision,
                 svn_txdelta_window_handler_t *window_handler,
                 void **window_baton,
                 apr_array_header_t *props,
                 apr_pool_t *pool) {

  JNIEnv *env;
  jobject jrevProps = NULL, jpropDiffs = NULL;
  jstring jpath;
  file_rev_baton *baton = (file_rev_baton*)vbaton;
  env = baton->env;
  jpath = make_jstring(env, path);
  jrevProps = hash2map(env, rev_props, pool, NULL);
  jpropDiffs = prop_array_to_map(env, props);

  (*env)->CallVoidMethod(env, baton->jcallback, mid_filerev_callback, jpath,
                         (jlong)revnum, jrevProps, merged_revision, jpropDiffs,
                         window_handler != NULL);
  if (window_handler) {
    struct window_handler_baton *wb;
    svn_stream_t *out_stream, *b64_stream;
   /* init a window handler baton */
    wb = apr_palloc(pool, sizeof (struct window_handler_baton));
    wb->env = env;
    wb->jcallback = baton->jcallback;

    /* init an output stream */
    out_stream = svn_stream_create(wb, pool);
    svn_stream_set_write(out_stream, svnj_write_stream);
    svn_stream_set_close(out_stream, svnj_close_stream);
    b64_stream = svn_base64_encode(out_stream, pool);
     svn_stream_set_baton(out_stream, wb);
 
    svn_txdelta_to_svndiff2(&(wb->handler), &(wb->handler_baton), b64_stream,
                            1, pool);
    *window_handler = svnj_window_handler;
    *window_baton = wb;
  }

  return SVN_NO_ERROR;
}
JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_getFileRevs
  (JNIEnv *env, jobject self, jstring jpath, jlong jstartRev,
   jlong jendRev, jboolean includeMergedRevs, jobject jcallback) {

  repo_ctx *ctx;
  jlong ptr;
  const char *path;
  svn_revnum_t start_rev, end_rev;
  file_rev_baton baton;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, );
  ctx = (repo_ctx*)ptr;
  if (jcallback == NULL) {
    throw_exception(env, "java/lang/NullPointerException", "null FileRevCallback");
    return;
  } else if (jpath == NULL) {
    throw_exception(env, "java/lang/NullPointerException", "null path");
    return;
  }

  path = make_cstring(env, jpath, ctx->pool);
  
  start_rev = jstartRev;
  end_rev = jendRev;
  baton.env = env;
  baton.jcallback = jcallback;

  SVN_JNI_ERR(svn_repos_get_file_revs2(ctx->repos, path, start_rev, end_rev,
                             includeMergedRevs, NULL/*authz func*/,
                             NULL/*authz baton*/,file_rev_handler,
                                       &baton, ctx->pool), );
  
}

/*
typedef struct prop_callback_baton {
  JNIEnv *env;
  jobject jcallback;
} prop_callback_baton;

static svn_error_t *
prop_callback(void *baton, const char *path,
              apr_hash_t *prop_hash, apr_pool_t *pool) {

  if (baton) {
    jstring jpath;
    jobject jmap;
    prop_callback_baton *pcb;
    JNIEnv *env;

    pcb = (prop_callback_baton*) baton;
    env = pcb->env;
    jmap = hash2map(env, prop_hash, pool);
    jpath = make_jstring(env, path);
    (*env)->CallVoidMethod(env, pcb->jcallback, mid_plist_single, jpath, jmap);
  }
  return SVN_NO_ERROR;
}

*/

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_getRevProperties
(JNIEnv *env, jobject self, jlong jrev, jobject jmap) {
  repo_ctx *ctx;
  jlong ptr;
  apr_hash_t *hash_table;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, );
  ctx = (repo_ctx*)ptr;
  SVN_JNI_ERR(svn_fs_revision_proplist(&hash_table, ctx->fs, jrev, ctx->pool), );
  hash2map(env, hash_table, ctx->pool, jmap);
}

JNIEXPORT void JNICALL
Java_com_googlecode_svnj_RepoContext_getPathProperties
(JNIEnv *env, jobject self, jstring jpath, jint jdepth, jobject jmap) {
  repo_ctx *ctx;
  jlong ptr;
  const char *path;
  apr_hash_t *hash_table;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, );
  if (!jpath) {
    throw_exception(env, "java/lang/NullPointerException", "NULL path");
    return;
  }

  ctx = (repo_ctx*)ptr;
  path = make_cstring(env, jpath, ctx->pool);
  SVN_JNI_ERR(svn_fs_node_proplist(&hash_table,
                                   ctx->rev_root,
                                   path,
                                   ctx->pool), );
  hash2map(env, hash_table, ctx->pool, jmap);
}

JNIEXPORT jobjectArray JNICALL
Java_com_googlecode_svnj_RepoContext_list0(JNIEnv *env, jobject self, jstring jpath) {
  apr_hash_t *ents;
  repo_ctx *ctx;
  const char *path, *name;
  apr_hash_index_t *hi;
  svn_fs_dirent_t *dirent;
  jlong ptr;
  jobjectArray ret = NULL;
  jclass selfclazz, dirclazz;
  jsize size = 0, offset = 0;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, NULL);
  if (!jpath) {
    throw_exception(env, "java/lang/NullPointerException", "NULL path");
    return NULL;
  }

  ctx = (repo_ctx*)ptr;
  path = make_cstring(env, jpath, ctx->pool);
  path = svn_dirent_canonicalize(path, ctx->pool);
  selfclazz = (*env)->GetObjectClass(env, self);
  dirclazz = (*env)->FindClass(env, SVNJPKG "/RepoContext$DirEntry");

  SVN_JNI_ERR(svn_fs_dir_entries(&ents, ctx->rev_root, path, ctx->pool), NULL);
  size = apr_hash_count(ents);
  ret = (*env)->NewObjectArray(env, size, dirclazz, NULL);

  for (hi = apr_hash_first(ctx->pool, ents); hi != NULL;
       hi = apr_hash_next(hi)) {
    jobject entry;
    const char* fullpath;
    jlong content_length = -1;
    jstring jfullpath;

    apr_hash_this(hi, (const void**)&name, NULL, (void**)&dirent);
    fullpath = svn_dirent_join(path, dirent->name, ctx->pool);
    if (dirent->kind == svn_node_file) {
      SVN_JNI_ERR(svn_fs_file_length((svn_filesize_t *) &content_length, ctx->rev_root, fullpath, ctx->pool), NULL);
    }
    jfullpath = make_jstring(env, fullpath);
    entry =
      (*env)->CallStaticObjectMethod(env, selfclazz, mid_createEntry,
                                     jfullpath, dirent->kind, content_length);

    (*env)->SetObjectArrayElement(env, ret, offset, entry);
    offset++;
  }
  return ret;
}

JNIEXPORT jlong JNICALL
Java_com_googlecode_svnj_RepoContext_getCreatedRev(JNIEnv *env, jobject self,
                                                     jstring jpath) {
  repo_ctx *ctx;
  const char *path;
  jlong ptr;
  svn_revnum_t rev = SVN_INVALID_REVNUM;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, -1);
  if (jpath != NULL) {
    ctx = (repo_ctx*)ptr;
    path = make_cstring(env, jpath, ctx->pool);
  
    SVN_JNI_ERR(svn_fs_node_created_rev(&rev, ctx->rev_root, path, ctx->pool), -1);
  }
  return rev;
}

JNIEXPORT jobject JNICALL
Java_com_googlecode_svnj_RepoContext_getEntry(JNIEnv *env, jobject self,
                                              jstring jpath) {
  repo_ctx *ctx;
  const char *path;
  jlong ptr;
  jclass selfclazz;
  svn_node_kind_t kind;
  jlong content_length = -1;

  ptr = (*env)->GetLongField(env, self, fid_ptr);
  CHECK_PTR(ptr, (jobject) -1);
  if (jpath != NULL) {
    ctx = (repo_ctx*)ptr;
    path = make_cstring(env, jpath, ctx->pool);
    /* get node type */
    SVN_JNI_ERR(svn_fs_check_path(&kind, ctx->rev_root, path, ctx->pool), NULL);
    if (kind == svn_node_file) {
      SVN_JNI_ERR(svn_fs_file_length((svn_filesize_t *) &content_length, ctx->rev_root, path, ctx->pool), NULL);
    }
    selfclazz = (*env)->GetObjectClass(env, self);
    return (*env)->CallStaticObjectMethod(env, selfclazz, mid_createEntry,
                                          jpath, kind, content_length);
  }
  return NULL;
  
}

JNIEXPORT jint JNICALL
Java_com_googlecode_svnj_RepoContext_nodeType(JNIEnv *env, jobject self,
                                            jstring jpath) {
  repo_ctx *ctx;
  const char *path;
  svn_node_kind_t kind;
  jlong ptr;

  kind = svn_node_none;
  ptr = (*env)->GetLongField(env, self, fid_ptr);  
  CHECK_PTR(ptr, -1);

  if (jpath != NULL) {
    ctx = (repo_ctx*)ptr;
    path = make_cstring(env, jpath, ctx->pool);
    SVN_JNI_ERR(svn_fs_check_path(&kind, ctx->rev_root, path, ctx->pool), -1);
  }
  return kind;
}

JNIEXPORT jint JNICALL
Java_com_googlecode_svnj_RepoContext_writeContents(JNIEnv *env, jobject self,
                                                 jstring jpath, jobject jstream) {
  repo_ctx *ctx;
  svn_stream_t *stream;
  const char *path;
  char buf[2048];
  jlong ptr = (*env)->GetLongField(env, self, fid_ptr);
  apr_size_t nread, total = 0;
  jbyteArray jbytes;

  if (!ptr) {
    throw_exception(env, "java/lang/RuntimeException", "context closed");
    return -1;
  }
  if (!jpath) {
    throw_exception(env, "java/lang/NullPointerException", "NULL path");
    return -1;
  }
  ctx = (repo_ctx*)ptr;
  path = make_cstring(env, jpath, ctx->pool);

  jbytes = (*env)->NewByteArray(env, sizeof(buf));

  SVN_JNI_ERR(svn_fs_file_contents(&stream, ctx->rev_root, path, ctx->pool), -1);

  do {
    svn_error_t *err;
    nread = sizeof(buf);
    err = svn_stream_read(stream, buf, &nread);
    if (err != SVN_NO_ERROR) {
      svn_stream_close(stream);
      throw_svn_exception(env, err, FALSE);
      return -1;
    }
    (*env)->SetByteArrayRegion(env, jbytes, 0, nread, (jbyte*)buf);
    (*env)->CallVoidMethod(env, jstream, mid_ostream_write, jbytes, 0, nread);
    total += nread;
    if ((*env)->ExceptionCheck(env)) {
      svn_stream_close(stream);
      return -1;
    }
  } while (nread > 0);

  SVN_JNI_ERR(svn_stream_close(stream), -1);
  return total;
}

